using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ebcpatterns.infrastructure;
using StructureMap;

namespace app
{
    public class StructureMapComponentLoader
    {
        private Container container;
        private InstanceInterceptor instanceInterceptor;

        public void Execute(params string[] args) {
            LoadTypes();
            BuildAndBind();
            Inject();
            Configure(args);
            Run(args);
        }

        internal void LoadTypes() {
            instanceInterceptor = new InstanceInterceptor();
            container = new Container(
                c => {
                    c.Scan(scanner => {
                        scanner.AssembliesFromApplicationBaseDirectory();
                        scanner.WithDefaultConventions();
                        scanner.AddAllTypesOf<IMainboard>();
                        scanner.AddAllTypesOf<IConfigurable>();
                        scanner.AddAllTypesOf<IEntryPoint>();
                        scanner.AddAllTypesOf<IDependsOn>();
                        scanner.RegisterConcreteTypesAgainstTheFirstInterface();
                        scanner.SingleImplementationsOfInterface();
                        scanner.ExcludeNamespace("StructureMap");
                    });
                    c.RegisterInterceptor(instanceInterceptor);
                    c.AddRegistry(new EbcPatternsRegistry());
                });
        }

        internal void BuildAndBind() {
            container.GetInstance<IMainboard>();
        }

        internal IEnumerable<T> AllInstancesOfType<T>() where T : class {
            return instanceInterceptor.Instances.ToArray().OfType<T>();
        }

        internal void Inject() {
            foreach (var instance in AllInstancesOfType<IDependsOn>()) {
                var injectMethod = instance.GetType().GetMethod("Inject", BindingFlags.Instance | BindingFlags.Public);
                var parameterType = injectMethod.GetParameters()[0].ParameterType;
                var parameter = container.GetInstance(parameterType);
                injectMethod.Invoke(instance, new[] {parameter});
            }
        }

        internal void Configure(params string[] args) {
            foreach (var instance in AllInstancesOfType<IConfigurable>()) {
                instance.Configure(args);
            }   
        }

        internal void Run(params string[] args) {
            foreach (var instance in AllInstancesOfType<IEntryPoint>()) {
                instance.Run(args);
            }
        }
    }
}